﻿// 
//  Code file with helpers for Ad Hoc lookahead in gplex/gppg scanner/parsers
//  Copyright (c) 2012 K John Gough, QUT.
//
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;

namespace QUT.Gppg {
    // ===================================================================================================
    // Template code to cut and paste.
    // ===================================================================================================
    // Place this code in the prolog of the file.lex specification, at the start of the rules section.
    //
    //      // Code to take tokens from non-empty queue.
    //      if (this.pushback.QueueLength > 0) {
	//          ScanObj obj = this.pushback.DequeueCurrentToken();
	//          this.yylloc = obj.yylloc;
	//          this.yylval = obj.yylval;
	//          return obj.token;
    //      }
    //
    // ===================================================================================================
    // Each scanner should declare a field of PushbackQueue type, which must be accessible to semantic
    // actions of the parser.  Initialize this field with this code as part of scanner initialization.
    //
    //    this.pushback = QUT.Gppg.PushbackQueue<ScanObj>.NewPushbackQueue(
    //        () => new ScanObj( this.yylex(), this.yylval, this.yylloc ),
    //        (int t) => new ScanObj( t, this.yylval, this.yylloc )
    //    );
    //
    // ===================================================================================================

    // ===================================================================================================
    /// <summary>
    /// PushbackQueue provides the infrastructure for pushing back token 
    /// information to a host scanner. The class provides an API for 
    /// initializing new lookahead buffers and adding those to the queue.
    /// The host scanner must be modified so that yylex takes tokens from
    /// the queue while the queue is not empty.
    /// </summary>
    internal class PushbackQueue<Obj> {
        // 
        //  The argument for type-param Obj will be the ScanObj
        //  class generated by GPPG. Each object encapsulates --
        //    * a field "token" of type int.
        //    * a field "yylval" of TValue type.
        //    * a field "yylloc" of TSpan type.
        //

        public delegate Obj GetObj();
        public delegate Obj TokWrap( int tok );

        GetObj getObj;
        TokWrap tokWrap;

        /// <summary>
        /// Create a new pushback queue which enqueues token information 
        /// from the host scanner. This should be called once per scanner
        /// instance, at scanner initialization.
        /// </summary>
        /// <param name="getObj">A delegate that constructs an Obj from a call to scanner.yylex</param>
        /// <param name="tokWrap">A delegate that constructs an Obj from the given integer token</param>
        /// <returns>A reference to the queue object</returns>
        public static PushbackQueue<Obj> NewPushbackQueue( GetObj getObj, TokWrap tokWrap ) {
            return new PushbackQueue<Obj>( getObj, tokWrap );
        }

        private PushbackQueue( GetObj getObj, TokWrap tokWrap ) { 
            this.getObj = getObj;
            this.tokWrap = tokWrap;
        }

        /// <summary>
        /// The current queue from which ScanObj objects are dequeued
        /// </summary>
        Queue<Obj> currentSymbolQueue;

        /// <summary>
        /// The buffer into which lookahead ScanObj are placed
        /// </summary>
        Queue<Obj> pushbackBuffer;
#if TRACE_ACTIONS
        /// <summary>
        /// Used for tracing diagnostics only.
        /// </summary>
        internal int BufferLength { get { return pushbackBuffer.Count; } }
#endif

        /// <summary>
        /// The stack of pushback buffers. Whenever currentSymbolQueue
        /// is exhausted the top of this stack is popped into 
        /// currentSymbolQueue
        /// </summary>
        Stack<Queue<Obj>> allQueues = new Stack<Queue<Obj>>();
#if TRACE_ACTIONS
        /// <summary>
        /// Used for tracing diagnostics only.
        /// </summary>
        internal int QueueCount { get { return allQueues.Count; } }
#endif

        /// <summary>
        /// Total number of queued symbols.
        /// </summary>
        int totalQueueLength = 0;
        internal int QueueLength { get { return totalQueueLength; } }

        /// <summary>
        /// Fetch a symbol from the host scanner and enqueue.
        /// </summary>
        /// <returns>the enqueued ScanObj</returns>
        internal Obj GetAndEnqueue() {
            Obj result;
            //
            // We could always call host.yylex(), but if the ScanObj
            // is already allocated, use it and spare the GC.
            //
            if (totalQueueLength > 0)
                result = this.DequeueCurrentToken();
            else
                result = this.getObj();
            pushbackBuffer.Enqueue( result );
            return result;
        }

        /// <summary>
        /// Fetch a ScanObj object from the enqueued symbol
        /// information, and perform queue housekeeping.
        /// </summary>
        /// <returns>the dequeued ScanObj</returns>
        internal Obj DequeueCurrentToken() {
            Obj result = currentSymbolQueue.Dequeue();
            totalQueueLength--;
            if (currentSymbolQueue.Count == 0 && totalQueueLength > 0)
                currentSymbolQueue = allQueues.Pop();
            return result;
        }

        /// <summary>
        /// Begin a new lookahead by allocating a pushback buffer
        /// if necessary. If nextToken is zero the next symbol has
        /// not been fetched so fetch it now. 
        /// Enqueue the symbol information in the pushback buffer
        /// and return the ScanObj object.
        /// </summary>
        /// <param name="nextToken">next token, or zero if not fetched yet</param>
        /// <returns>next symbol information</returns>
        internal Obj EnqueueAndReturnInitialSymbol( int token ) {
            // Creating a new pushbackBuffer is necessary, unless
            // the last pushback buffer was abandoned and cleared.
            Obj next;
            if (pushbackBuffer == null)
                pushbackBuffer = new Queue<Obj>();
            if (token != 0)
                next = this.tokWrap( token );
            else if (totalQueueLength > 0)
                next = this.DequeueCurrentToken();
            else
                next = this.getObj();
            this.pushbackBuffer.Enqueue( next );
            return next;
        }

        /// <summary>
        /// Add pushback buffer to the token queue
        /// </summary>
        internal void AddPushbackBufferToQueue() {
            // Stack previous currentSymbolQueue if notEmpty, and
            // make currentSymbolQueue reference the pushback buffer.
            if (currentSymbolQueue != null && currentSymbolQueue.Count > 0)
                allQueues.Push( currentSymbolQueue );
            currentSymbolQueue = pushbackBuffer;
            totalQueueLength += pushbackBuffer.Count;
            pushbackBuffer = null;
        }

        /// <summary>
        /// Abandon the current length-1 pushback buffer.
        /// The parser NextToken field must have been restored!
        /// </summary>
        internal void AbandonPushback() {
            this.pushbackBuffer.Clear();
        }
    }
    // ===================================================================================================
}
